Skip to content

fix: improve formbuilder logic #1135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 22, 2025
Merged

fix: improve formbuilder logic #1135

merged 14 commits into from
Apr 22, 2025

Conversation

tefkah
Copy link
Member

@tefkah tefkah commented Apr 3, 2025

  • fix: improve client side logic for formbuilder
  • fix: be able to submit access changes on their own

Issue(s) Resolved

Resolves #1143

High-level Explanation of PR

Roughly does 3 things:

  1. Moves the form builder diff calculation on the client, that way it's easier to only send up changes that are relevant, and we disable the submit button if no changes have been made
  2. Updates access if that is the only thing changed. Previously also the formelements needed to have been moved/deleted/added in order for a change in access to go through
  3. slight beauty enhancements

Moving diff to client

This was put as TODOs in the code in some places, and it made my life easier, so i did.

The main change to the diffing algorithm is here:

// check whether the element is reeeaally updated minus the updated field
const { updated: _, id: _id, ...elementWithoutUpdated } = element;
const { updated, id, ...rest } =
defaultValues.elements.find((e) => e.elementId === element.elementId) ?? {};
const defaultElement = rest as Omit<FormElementData, "updated" | "id">;
if (JSON.stringify(defaultElement) === JSON.stringify(elementWithoutUpdated)) {
return acc;
}

May be a better way of doing this but i thought this was fine for now (i expect the order of keys to be relatively stable).

Basically, if nothing except maybe the id/updatedAt of the form element has changed, we do not consider it a change.

Note

Caveat: A "rank" change is still considered a change. If two elements are swapped back and forth, their rank may still have been changed. I find it hard to check for this case as i'll admit i don't fully understand the ranking implementation, maybe @kalilsn you have some insight on how to ignore swapping back and forth cases.

Warning

Moving the diffs to the client now does carry slightly more risk for weird stuff happening, but not really that much more than before

Update logic changes

Since all the changes now arrive as separate entities, we can more easily only update form accesstype if that has changed.

QoL changes

  • When hovering over the drag handle, we now show cursor-drag rather than cursor-pointer
  • When dragging, we now show cursor-dragging, rather than cursor-pointer
  • When dragging, the dragged element no longer sometimes slides under other elements when dragged.

Demo:
(slightly hard to see bc recording my screen apparently makes the cursor almost always be the default one)

Screen.Recording.2025-04-14.at.12.14.28.mov

Test Plan

Screenshots (if applicable)

Notes

Comment on lines -31 to -38
searchParams: Promise<{
unsavedChanges: boolean;
}>;
}) {
const searchParams = await props.searchParams;

const { unsavedChanges } = searchParams;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this logic does not need to happen server side, we can just do it client side. this is much faster

Comment on lines +162 to +164
if (JSON.stringify(defaultElement) === JSON.stringify(elementWithoutUpdated)) {
return acc;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very sophisticated diffing. please recommend a better approach if you know one

const form = useForm<FormBuilderSchema>({
resolver: zodResolver(formBuilderSchema),
values: {
const [isChanged, setIsChanged] = useIsChanged();
Copy link
Member Author

@tefkah tefkah Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a small custom nuqs hook reads from/updates the isChanged query param. using nuqs we can do this without a round trip to the server, which we want. this is because shallow is set to true by default. maybe i should add that option to make it slightly more clear that these are client-side only url updates

import { parseAsBoolean, useQueryState } from "nuqs";
export const useIsChanged = () => {
const [isChanged, setIsChanged] = useQueryState(
"unsavedChanges",
parseAsBoolean.withDefault(false).withOptions({
history: "replace",
scroll: false,
})
);
return [isChanged, setIsChanged] as const;
};

@@ -18,7 +21,7 @@ export const SaveFormButton = ({ form, className, disabled }: Props) => {
form={form}
type="submit"
data-testid="save-form-button"
disabled={disabled}
disabled={disabled != null ? disabled : !isChanged}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe i should just remov the disabled prop entirely and make it !isChanged by default

Comment on lines +175 to +178
if (
element.relatedPubTypes.length === 0 &&
defaultElement.relatedPubTypes?.length
) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i added this defaultElement.relatedPubTypes?.length check, bc otherwise if you swapped a relation element back and forth it would assume something had to be deleted, which didn't make sense

@tefkah tefkah requested review from allisonking and kalilsn April 14, 2025 10:50
@tefkah tefkah marked this pull request as ready for review April 14, 2025 10:51
Copy link
Contributor

@allisonking allisonking left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

woo this is awesome! approving, though I don't know about the ranking part either though, so maybe wait for Kalil to weigh in on that part?

@3mcd 3mcd merged commit bfd67ba into main Apr 22, 2025
16 checks passed
@3mcd 3mcd deleted the tfk/formbuilder-fixes branch April 22, 2025 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bug: cannot make form public
3 participants